import re

from flask import g
from pydantic import BaseModel, Field, validator, HttpUrl
from typing import List
import ipaddress
from ipaddress import IPv4Address, IPv6Address
from bson import ObjectId, errors
from wtforms import FieldList, StringField, IntegerField

from app.libs.enums import (
    SECURITY_EVENT_PLUGIN_TUPLE,
    CONTENT_PLUGIN_TUPLE,
    WS_TASK_GROUP_TUPLE,
    CHANGE_CHECK_TYPE_TUPLE
)
from app.libs.ip import check_source_ip
from app.libs.regexp import NAME_RE
from app.libs.score import Score
from app.errors import ParameterError, CustomNameError
from wtforms.validators import DataRequired, AnyOf

from app.validators import BaseForm


class TargetUrl(BaseModel):
    url: HttpUrl = Field(..., name='资产url')

    @validator('url')
    def validate_url(cls, v):
        """ip 类型 和 域名类型; IP 需要校验是否是正确的IP"""
        host = v.host
        if (v.host_type == 'ipv4' and IPv4Address(host).is_private) or (
                v.host_type == 'ipv6' and IPv6Address(host.replace('[', '').replace(']', '')).is_private):
            raise ParameterError(msg="IP不能为私有地址")
        return v


class HostAssetAdd(BaseModel):
    uid: str = Field(None, name='用户id', min_length=0, max_length=50)
    note: str = Field(..., name='资产名', min_length=1, max_length=50)
    asset_type: str = Field(validators=[DataRequired()], name='资产类型', choices=['host', ])
    target_urls: List[str] = Field(..., name='多个资产地址', min_items=1)

    @validator('uid')
    def validate_uid(cls, v):
        if v:
            return ObjectId(v)
        return ''

    @validator('note')
    def validate_note(cls, value):
        if not NAME_RE.fullmatch(value):
            raise ParameterError(msg='名称格式错误，只能输入1～50个中文、英文字母、数字、下划线')
        return value


class AssetJobId(BaseModel):
    jobId: str = Field(..., name='资产jobId')
    enableTask: bool = Field(default=False, name='是否开启监测任务')

    @validator('jobId')
    def validate_jobId(cls, v):
        try:
            ret = ObjectId(v)
        except errors.InvalidId:
            raise ParameterError(msg="请输入正确资产ID")
        return ret


class BatchAssetJobId(BaseModel):
    uid_list: List[str] = Field(None, name='用户列表', min_items=0)
    jobId: List[str] = Field(..., name='批量资产jobId', min_items=1)

    @validator('jobId')
    def validate_jobId(cls, v):
        try:
            ret = [ObjectId(_v) for _v in v]
        except errors.InvalidId:
            raise ParameterError(msg="请输入正确资产ID")
        return ret

    @validator('uid_list')
    def validate_uid_list(cls, v):
        result = set()
        for item in v:
            result.add(ObjectId(item))
        return list(result)


class AssetStatus(BaseModel):
    uid: str = Field(None, name='用户名', min_length=0, max_length=50)
    jobId: str = Field(..., name='待删除资产jobId')

    enableMonitor: bool = Field(None, name='是否开启监测状态')
    enableAlert: bool = Field(None, name='是否开启告警')

    @validator('jobId')
    def validate_jobId(cls, v):
        return ObjectId(v)

    @validator('uid')
    def validate_uid(cls, v):
        if v:
            return ObjectId(v)
        return ''


class AssetVerifiedFrom(BaseForm):
    uid = StringField()
    asset_type = StringField()

    def validate_uid(self, value):
        self.uid = ''
        if g.role.name == '管理员':
            if not value.data:
                raise ParameterError(msg="uid param error")
            try:
                self.uid = ObjectId(value.data)
            except Exception:
                raise ParameterError(msg="uid param error")

    def validate_asset_type(self, value):
        self.asset_type = 'web'
        if value.data and value.data not in ['web', 'host', 'all']:
            raise ParameterError(msg='资产类型选择错误')
        else:
            self.asset_type = value.data


class AssetAddForm(BaseForm):
    uid = StringField()
    note = StringField()
    target_url = StringField()
    source_ips = FieldList(StringField())

    def __init__(self, data=None):
        self.uid = None
        self.note = None
        self.url = None
        self.target_url = None
        self.source_ips = []
        super().__init__(data=data)

    def validate_uid(self, value):
        try:
            self.uid = ''
            if value.data:
                self.uid = ObjectId(value.data)
        except Exception:
            raise ParameterError(msg='uid参数格式错误')

    def validate_note(self, value):
        value.data = value.data.strip()
        if not NAME_RE.fullmatch(value.data):
            raise CustomNameError()
        self.note = value.data

    def validate_source_ips(self, value):
        source_ip_list = []
        if value.data:
            if len(value.data) > 100:
                raise ParameterError(msg='一次最多只能添加100个')
            for item in value.data:
                if item:
                    source_ip_list.append(item)
        if len(source_ip_list) == 1:
            status, message = check_source_ip(source_ip_list[0])
            if not status:
                raise ParameterError(msg=message)
        self.source_ips = source_ip_list

    def validate_target_url(self, value):
        value.data = value.data.strip()
        if not value.data:
            raise ParameterError(msg='资产地址不能为空')
        try:
            self.url = TargetUrl(url=value.data)
            self.target_url = value.data
        except ParameterError:
            raise ParameterError(msg="IP不能为私有地址")
        except Exception:
            raise ParameterError(msg='资产地址格式错误')


class AssetUpdateForm(BaseForm):
    uid = StringField()
    jobId = StringField(validators=[DataRequired()], label='Jobs._id')
    note = StringField()
    sourceIp = StringField()

    def __init__(self, data=None):
        self.uid = None
        self.note = None
        super().__init__(data=data)

    def validate_uid(self, value):
        try:
            self.uid = ''
            if value.data:
                self.uid = ObjectId(value.data)
        except Exception:
            raise ParameterError(msg='uid参数格式错误')

    def validate_jobId(self, value):
        try:
            self.jobId = ObjectId(value.data)
        except Exception:
            raise ParameterError(msg='jobId参数格式错误')

    def validate_note(self, value):
        value.data = value.data.strip()
        if not NAME_RE.fullmatch(value.data):
            raise CustomNameError()
        self.note = value.data

    @staticmethod
    def check_source_ip(source_ip):
        try:
            ip = ipaddress.ip_address(source_ip)
        except ValueError:
            raise ParameterError(msg='源站IP格式错误')
        if ip.is_private:
            raise ParameterError(msg='源站IP不能为私有地址')
        return source_ip


class AssetListForm(BaseForm):
    page = IntegerField(default=1, label='页码')
    perPage = IntegerField(default=10, label='每页最大展示条数')
    count = IntegerField(default=6, label='每页最大展示条数')  # 兼容

    asset_type = StringField(validators=[AnyOf(values=['host', 'web'])], label='资产类型')
    # level '未知', '安全', '严重', '高危', '中危', '低危'
    level = StringField(label='风险等级')
    alive_status = StringField(label='存活状态')
    note = StringField(label='资产名称')
    target_url = StringField(label='资产地址')
    source_ip = StringField(label='源站IP')

    is_verified = StringField(label='是否认证')
    is_support_ipv6 = StringField(label='是否支持ipv6')
    defense_target_id = StringField(label='源站安全')
    username = StringField(label='用户名称')
    package = StringField(label='套餐名称')
    start_time = StringField(label='开始时间')
    end_time = StringField(label='结束时间')
    monitor_type = StringField(label='监测结果')
    task_level = StringField(label='监测结果等级')
    enable_proxy_ip = StringField(label="是否开启IP代理")
    proxy_ip = StringField(label="正在使用的代理IP")

    def validate_monitor_type(self, value):
        if self.asset_type.data == "host":
            return
        if value.data and value.data not in WS_TASK_GROUP_TUPLE:
            raise ParameterError(msg="监测结果只能选择Web漏洞、SSL漏洞、安全事件、内容违规、篡改")

    def validate_task_level(self, value):
        if self.asset_type.data == "host":
            return
        task_level = value.data
        if not self.monitor_type.data and task_level:
            raise ParameterError(msg="没有选择监测结果，监测结果等级无效")
        if task_level is not None:
            if task_level in SECURITY_EVENT_PLUGIN_TUPLE + CONTENT_PLUGIN_TUPLE + CHANGE_CHECK_TYPE_TUPLE:
                return
            if Score.check_level_num(task_level):
                return
            else:
                raise ParameterError(msg="监测结果等级选择错误")

    def check_bool(self, value):
        if isinstance(value, bool):
            return value
        if value in ('False', 'false'):
            return False
        elif value in ('True', 'true'):
            return True
        else:
            return None

    def validate_alive_status(self, value):
        value.data = self.check_bool(value.data)

    def validate_is_verified(self, value):
        value.data = self.check_bool(value.data)

    def validate_defense_target_id(self, value):
        value.data = self.check_bool(value.data)

    def validate_enable_proxy_ip(self, value):
        value.data = self.check_bool(value.data)

    def validate_level(self, value):
        if value.data and value.data not in ('未知', '安全', '严重', '高危', '中危', '低危'):
            raise ParameterError(msg='安全等级参数错误')
